#include "connection.h"
#define SSLTEST 0

#define MAXLINE 1024

#define PASSPHRASE "ABCD"
#define CERTPATH "server.crt"
#define KEYPATH "server.key"

Connection::Connection(QObject *parent, QSslSocket *socket) :
    QObject(parent), socket(socket), db(db)
{
    certPath = CERTPATH;
    keyPath = KEYPATH;
    adminConnected = false;
    clientConnected = false;
}

void Connection::loginReply(bool success, int privilege)
{
    QString packet = "login:";

    if (success) {
        packet.append("success;privilege:");
        switch (privilege) {
        case 0:
            clientConnected = true;
            packet.append("user");break;
        case 1:
            adminConnected = true;
            packet.append("admin");break;
        default:
            packet.append("unknown");
        }
    }
    else {
        packet.append("failed;failureReason:Login failed");
    }

    packet.append("\n");
    socket->write(packet.toLatin1());
}

void Connection::sslErrors(const QList<QSslError> & errList)
{
    foreach (QSslError err, errList) {
        emit sslError(err.errorString());
    }
}

/**
  To be called when thread starts
  */
void Connection::setupComms()
{
    // set up database connection
    db = new DatabaseInterface(this);

    // set up encryption on socket
    qDebug() << "Set protocol";
    socket->setProtocol(QSsl::TlsV1);

    // load CA certificates
    qDebug() << "Set cert/privkey";
    socket->setLocalCertificate(certPath);
    socket->setPrivateKey(keyPath, QSsl::Rsa, QSsl::Pem, PASSPHRASE);

    // check that the certificate / private key are not null
    if (socket->localCertificate().isNull()) {
        qDebug() << "WARNING: The local certificate appears to be null! ";
    }
    if (socket->privateKey().isNull()) {
       qDebug() << "WARNING: The private key appears to be null! ";
    }

    connect(socket, SIGNAL(encrypted()), this, SLOT(encrypted()));

    connect(this, SIGNAL(logInAttempt(const QString &, const QString &)),
            db, SLOT(attemptLogIn(const QString &, const QString &)));
    connect(this, SIGNAL(requestLogs(QStringList*)),
            db, SLOT(retrieveLogs(QStringList*)));
    connect(this, SIGNAL(newLog(const QString &, const QString &)),
            db, SLOT(addNewLog(const QString &, const QString &)));
    connect(this, SIGNAL(accessListRequest(const QString&)),
            db, SLOT(getAccessList(const QString&)));
    connect(this, SIGNAL(addAccessRequest(const QString&, const QString&)),
            db, SLOT(addNewAccessRight(const QString &,const QString&)));
    connect(this, SIGNAL(newUserRequest(const QString&, const QString&,int)),
            db, SLOT(addNewUser(const QString&, const QString&, int)));
    connect(this, SIGNAL(removeAccessRequest(QString,QString)),
            db, SLOT(removeAccessRight(QString,QString)));
    connect(this, SIGNAL(removeUserRequest(QString)),
            db, SLOT(removeUser(QString)));

    connect(db, SIGNAL(logInVerified(bool,int)), this, SLOT(loginReply(bool,int)));
    connect(db, SIGNAL(sendLog(const QString &, const QString &, const QString &)),
            this, SLOT(sendLog(const QString &, const QString &, const QString &)));
    connect(db, SIGNAL(queryDone()), this, SLOT(queryDone()));
    connect(db, SIGNAL(newLogSuccess(const QString &)), this, SLOT(newLogSuccess(const QString &)));
    connect(db, SIGNAL(newLogFailed()), this, SLOT(newLogFailed()));
    connect(db, SIGNAL(accessListFailed(QString)), this, SLOT(accessListFailure(QString)));
    connect(db, SIGNAL(accessListSuccess(QStringList*)), this, SLOT(accessListSuccess(QStringList*)));
    connect(db, SIGNAL(addNewAccessFailed(QString)), this, SLOT(addAccessFailed(QString)));
    connect(db, SIGNAL(addNewAccessSuccess()), this, SLOT(addAccessSuccess()));
    connect(db, SIGNAL(newUserFailed(QString)), this, SLOT(addUserFailure(QString)));
    connect(db, SIGNAL(newUserSuccess(QString)), this, SLOT(addUserSuccess(QString)));
    connect(db, SIGNAL(removeAccessRightFailed(QString)), this, SLOT(removeAccessFailed(QString)));
    connect(db, SIGNAL(removeAccessRightSuccess()), this, SLOT(removeAccessSuccess()));
    connect(db, SIGNAL(removeUserFailed(QString)), this, SLOT(removeUserFailed(QString)));
    connect(db, SIGNAL(removeUserSuccess()), this, SLOT(removeUserSuccess()));

    connect(socket, SIGNAL(stateChanged(QAbstractSocket::SocketState)), this, SLOT(socketStateChanged(QAbstractSocket::SocketState)));
    qDebug() <<"calling startServerEncryption()";
    socket->startServerEncryption();
}

void Connection::readFromSocket()
{
#if DEBUG == 1
    qDebug() << "Received readyRead signal, reading now..";
#endif
#if SSLTEST == 1
    if (socket->canReadLine())
    {
        qWarning ("Received %s over the network.",
                  qPrintable(QString::fromUtf8(socket->readLine(MAXLINE))));
    }
#else
    if (socket->canReadLine())
    {
#if DEBUG == 1
        qDebug() << "Reading line";
#endif
        QString line = QString::fromUtf8(socket->readLine()).trimmed();
        QString reqSection = line.section(';', 0, 0);
        QStringList *data = new QStringList(line.section(';', 1).split(';'));
        QString check = reqSection.section(':', 0,0);
        QString field = reqSection.section(':', 1);

        qDebug() << "line: " << line;
        qDebug() << "check: " << check;
        qDebug() << "field: " << field;
        qDebug() << "data: " << *data;

        if (check != "req") {
            socket->write("Error: Received bad packet from client.");
            delete data;
            return;
        }

        if (clientConnected)
            qDebug() << "clientConnected";
        if (adminConnected)
            qDebug() << "adminConnected";
        if (field == "login" && !clientConnected && !adminConnected)
            checkLoginData(data);
        else if (field == "newlog" && clientConnected) {
            qDebug() << "newlog: " << *data;
            QString un = data->first().split(':').last();
            QString pw = data->last().split(':').last();
            emit newLog(un, pw);
        }
        else if (field == "accesslist") {
            QString un = data->first().split(':').last();
            emit accessListRequest(un);
        }
        else if (field == "userLogs") {
            qDebug() << "Recieved: " + line;
            QStringList *names = new QStringList(data->first().split(':').last()
                                              .split(','));
            emit requestLogs(names);
        }
        else if (field == "addUser") {
            QString userName = data->first().split(':').last();
            data->removeFirst();
            QString pw = data->first().split(':').last();
            data->removeFirst();
            int privilege = data->first().split(':').last().toInt();

            qDebug() << "Asking db to add new user: " + userName + " " + pw + " " + QString::number(privilege);
            emit newUserRequest(userName, pw, privilege);
        }
        else if (field == "removeUser") {
            QString userName = data->first().split(':').last();
            emit removeUserRequest(userName);
        }
        else if (field == "addAccess") {
            QString name = data->first().split(':').last();
            data->removeFirst();
            QString target= data->first().split(':').last();
            emit addAccessRequest(name, target);
        }
        else if (field == "removeAccess") {
            QString name = data->first().split(':').last();
            data->removeFirst();
            QString target= data->first().split(':').last();
            emit removeAccessRequest(name, target);
        }
    }
#endif
}

void Connection::encrypted()
{
    qDebug() << "encrypted.";
    connect (socket, SIGNAL(readyRead()), this, SLOT(readFromSocket()), Qt::QueuedConnection);
}

void Connection::checkLoginData(QStringList *idPass)
{
    qDebug() << "Checking login data...";

    QString ID = idPass->first().section(':', 1);
    QString hashedPW = idPass->last().section(':',1);

    delete idPass;
    db->attemptLogIn(ID, hashedPW);
}

void Connection::sendLog(const QString &userName, const QString &log, const QString &timeStamp)
{
    QString packet = "logEntry#;#";
    packet.append(timeStamp).append("#:#").append(userName).append("#:#").append(log).append("\n");

    socket->write(packet.toLatin1());
}

void Connection::queryDone()
{
    QString packet = "endLogs";
    packet.append("\n");
    socket->write(packet.toLatin1());
}

void Connection::newLogSuccess(const QString &timeStamp)
{
    QString packet = "newLogSuccess;";
    packet.append(timeStamp).append("\n");
    socket->write(packet.toLatin1());
}

void Connection::newLogFailed()
{
    QString packet = "newLogFailed;";
    packet.append("\n");
    socket->write(packet.toLatin1());
}

void Connection::accessListSuccess(QStringList *list)
{
    QString packet = "accessList";

    foreach (QString name, *list) {
        packet.append(";").append("uname:").append(":").append(name);
    }

    packet.append("\n");

    socket->write(packet.toLatin1());
}

void Connection::accessListFailure(const QString &reason)
{
    QString packet = "accessList;failed:";
    packet.append(reason).append("\n");
    socket->write(packet.toLatin1());
}

void Connection::addUserSuccess(const QString &name)
{
    QString packet = "newUserAdded:success;";
    packet.append(name).append("\n");
    socket->write(packet.toLatin1());
}

void Connection::addUserFailure(const QString &reason)
{
    QString packet = "newUserAdded:failed;";
    packet.append(reason).append("\n");
    socket->write(packet.toLatin1());
}

void Connection::removeUserSuccess()
{
    socket->write("userRemoved:success;\n");
}

void Connection::removeUserFailed(const QString &reason)
{
    QString packet = "userRemoved:failed;";
    packet.append(reason).append("\n");
    socket->write(packet.toLatin1());
}

void Connection::addAccessSuccess()
{
    socket->write("accessAdded:success\n");
}

void Connection::addAccessFailed(const QString &reason)
{
    QString packet = "accessAdded:failed;";
    packet.append(reason).append("\n");

    socket->write(packet.toLatin1());
}

void Connection::removeAccessSuccess()
{
    socket->write("accessRemoved:success\n");
}

void Connection::removeAccessFailed(const QString &reason)
{
    QString packet = "accessRemoved:failed;";
    packet.append(reason).append("\n");

    socket->write(packet.toLatin1());
}

void Connection::socketStateChanged(QAbstractSocket::SocketState state)
{
    if (state == QAbstractSocket::UnconnectedState) {
        emit disconnected();
    }
}
